package com.wyz.calibur.infrastructure.config.security;

import com.wyz.calibur.constant.SecurityConstant;
import com.wyz.calibur.domain.user.service.UserAuthInfoService;
import com.wyz.calibur.infrastructure.config.common.IgnoreUrlConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import java.util.List;

/**
 * @Description: Spring Security 配置
 * @Author: wei yz
 * @Date: 2022/6/19 16:13
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

    /**
     * 用戶信息服务
     */
    @Autowired
    private UserAuthInfoService userAuthInfoService;
    /**
     * 白名单
     */
    @Autowired
    private IgnoreUrlConstant ignoreUrlConstant;

    /**
     * 权限配置
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // 校验用户
        auth.authenticationProvider(authenticationProvider());
    }

    /**
     * 配置静态资源
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 设置系统的静态资源。静态资源不会走权限框架
        // web.ignoring().antMatchers(new String[]{});
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        log.info("==> 进入SecurityConfig <==");

        // 验证码过滤器
        // http.addFilterBefore(new ValidateCodeFilter(), UsernamePasswordAuthenticationFilter.class);

        // 第一层免过滤列表,URL白名单
        // 就是所有人都可以访问的地址。区别于静态资源
        List<String> urlList = ignoreUrlConstant.getUrl();
        log.info("==> 放行的URL列表: {}", urlList);

        //使用自定义的 Token过滤器 验证请求的Token是否合法
        http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        // http.addFilterBefore(noteSecurityFilter(), FilterSecurityInterceptor.class);

        // 登录页面使用form提交的方式
        http.formLogin()
                .usernameParameter(SecurityConstant.USERNAME)
                // 设置登录页面用户名和密码的input对应name值（其实默认值就是username,password，所以这里可以不用设置）
                .passwordParameter(SecurityConstant.PASSWORD)
                // 设置登录页面的地址
                //.loginPage("/login")
                // 登录页面输入用户名密码后提交的地址
                .loginProcessingUrl(SecurityConstant.LOGIN_URL)
                // 登录成功处理
                .successHandler(noteAuthenticationSuccessHandler())
                // 登录失败的处理
                .failureHandler(noteAuthenticationFailureHandler())
                // 以上url全部放行，不需要校验权限
                .permitAll()
                .and()
                // 注销相关配置
                .logout()
                // 注销地址
                //.logoutUrl("/logout")
                // 注销成功后跳转地址（这里就是跳转到登录页面）
                .logoutSuccessUrl(SecurityConstant.LOGOUT_URL)
                // 注销成功后的处理
                .addLogoutHandler(noteLogoutHandler())
                .logoutSuccessHandler(noteLogoutSuccessHandler())
                // 以上地址全部放行
                .permitAll()
                .and()
                .authorizeRequests()
                // 第一层免过滤列表, 白名单
                // 不需要登录，就可以直接访问的地址
                .antMatchers(CollectionUtils.isEmpty(urlList) ? new String[]{} : urlList.toArray(new String[urlList.size()]))
                // 全部放行
                .permitAll()
                // 其他都需要权限控制
                .anyRequest()
                .authenticated()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 权限查询器、设置你有哪些权限
                        object.setSecurityMetadataSource(noteSecurityMetadataSource());
                        // 权限决策器、判断你有没有权限访问当前的url
                        object.setAccessDecisionManager(noteAccessDecisionManager());
                        // 关闭URL的公开访问，所有 URL 必须具备对应的权限才能访问。
                        object.setRejectPublicInvocations(true);
                        return object;
                    }
                })
                // 异常处理
                .and()
                .exceptionHandling()
                // 匿名用户访问无权限的资源
                .authenticationEntryPoint(new CliburAuthenticationEntryPoint())
                // 登录用户访问无权限的资源
                .accessDeniedHandler(new CliburAccessDeniedHandler())
                // 禁用csrf
                .and()
                .csrf()
                .disable()
                .sessionManagement()
                //因为使用JWT，所以不需要HttpSession
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //http.headers().cacheControl();
    }


    @Bean
    public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtTokenFilter();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userAuthInfoService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                log.info("============ 密码验证 ============== ", charSequence.toString());
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                log.info("==> 对密码进行判断匹配：加密后的输入的密码 : 数据库中存在的密码  ==> {}:{}", encode, s);
                return s.equals(encode);
            }
        };
    }

    /**
     * @return
     * @Title: noteAuthenticationSuccessHandler
     * @Description: 登录成功后的处理
     */
    @Bean
    public CliburAuthenticationSuccessHandler noteAuthenticationSuccessHandler() {
        return new CliburAuthenticationSuccessHandler();
    }

    /**
     * @return
     * @Title: noteAuthenticationFailureHandler
     * @Description: 登录异常处理
     */
    @Bean
    public CliburAuthenticationFailureHandler noteAuthenticationFailureHandler() {
        return new CliburAuthenticationFailureHandler();
    }

    /**
     * @return
     * @Title: noteLogoutSuccessHandler
     * @Description: 退出成功后的处理
     */
    @Bean
    public CliburLogoutSuccessHandler noteLogoutSuccessHandler() {
        return new CliburLogoutSuccessHandler();
    }

    /**
     * @return
     * @Title: noteLogoutHandler
     * @Description: 退出处理
     */
    @Bean
    public CliburLogoutHandler noteLogoutHandler() {
        return new CliburLogoutHandler();
    }

    /**
     * @return
     * @Title: noteSecurityMetadataSource
     * @Description: 权限觉得管理器
     */
    @Bean
    public CliburSecurityMetadataSource noteSecurityMetadataSource() {
        return new CliburSecurityMetadataSource();
    }

    /**
     * @return
     * @Title: noteAccessDecisionManager
     * @Description: 访问决策管理器
     */
    @Bean
    public CliburAccessDecisionManager noteAccessDecisionManager() {
        return new CliburAccessDecisionManager();
    }
}
